﻿using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using Red.Controls.Shell;

namespace Red.Launcher.Dialogs
{
    /// <summary>
    ///     Custom dialog that asks the user to select a directory
    /// </summary>
    public partial class DirectoryPicker : RedWindow
    {
        #region Data

        private bool? _result; // we cannot use DialogResult as we want to show the dialog also non-modally
        private CalculateSpaceRequiredDelegate _calculateSpaceRequired;
        private string _driveLetter;

        #endregion Data

        #region Properties

        public static readonly DependencyProperty DirectoryProperty = DependencyProperty.Register( "Directory", typeof( string ), typeof( DirectoryPicker ), new PropertyMetadata( null, OnDirectoryChanged ) );
        public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( "Message", typeof( string ), typeof( DirectoryPicker ) );
        public static readonly DependencyProperty HasSpaceCalculatorProperty = DependencyProperty.Register( "HasSpaceCalculator", typeof( bool ), typeof( DirectoryPicker ) );
        public static readonly DependencyProperty SpaceRequiredCalculatingProperty = DependencyProperty.Register( "SpaceRequiredCalculating", typeof( bool ), typeof( DirectoryPicker ), new PropertyMetadata( true ) );
        public static readonly DependencyProperty SpaceAvailableCalculatingProperty = DependencyProperty.Register( "SpaceAvailableCalculating", typeof( bool ), typeof( DirectoryPicker ), new PropertyMetadata( true ) );
        public static readonly DependencyProperty SpaceRequiredProperty = DependencyProperty.Register( "SpaceRequired", typeof( ulong ), typeof( DirectoryPicker ) );
        public static readonly DependencyProperty SpaceAvailableProperty = DependencyProperty.Register( "SpaceAvailable", typeof( ulong ), typeof( DirectoryPicker ) );

        public string Directory
        {
            get { return (string)GetValue( DirectoryProperty ); }
            set { SetValue( DirectoryProperty, value ); }
        }

        /// <summary>
        ///     Description explaining what the directory will be used for
        /// </summary>
        public string Message
        {
            get { return (string)GetValue( MessageProperty ); }
            set { SetValue( MessageProperty, value ); }
        }

        public bool HasSpaceCalculator
        {
            get { return (bool)GetValue( HasSpaceCalculatorProperty ); }
            set { SetValue( HasSpaceCalculatorProperty, value ); }
        }

        public bool SpaceRequiredCalculating
        {
            get { return (bool)GetValue( SpaceRequiredCalculatingProperty ); }
            set { SetValue( SpaceRequiredCalculatingProperty, value ); }
        }

        public bool SpaceAvailableCalculating
        {
            get { return (bool)GetValue( SpaceAvailableCalculatingProperty ); }
            set { SetValue( SpaceAvailableCalculatingProperty, value ); }
        }

        public ulong SpaceRequired
        {
            get { return (ulong)GetValue( SpaceRequiredProperty ); }
            set { SetValue( SpaceRequiredProperty, value ); }
        }

        public ulong SpaceAvailable
        {
            get { return (ulong)GetValue( SpaceAvailableProperty ); }
            set { SetValue( SpaceAvailableProperty, value ); }
        }

        public CalculateSpaceRequiredDelegate CalculateSpaceRequired
        {
            get { return _calculateSpaceRequired; }
            private set
            {
                _calculateSpaceRequired = value;
                HasSpaceCalculator = value != null;

                if( value != null )
                {
                    RecalculateSpaceRequired();
                }
            }
        }

        #endregion Properties

            #region C-tor

        internal DirectoryPicker()
        {
            DataContext = this;
            InitializeComponent();
        }

        #endregion C-tor

        #region Public Methods

        /// <summary>
        ///     Shows a message box owned by top-level window in the elem hierarchy
        /// </summary>
        public static string Show( FrameworkElement elem, string message, string title = null, string defaultDirectory = null, CalculateSpaceRequiredDelegate calculateSizeRequired = null )
        {
            DirectoryPicker picker = Create( elem, message, title, defaultDirectory );
            picker.CalculateSpaceRequired = calculateSizeRequired;

            picker.ShowDialog();
            return true.Equals( picker._result )? picker.Directory : null;
        }

        /// <summary>
        ///     Shows a message box owned by top-level window in the elem hierarchy. 
        ///     Async version is like showing the dialog non-modally, with task finished when user takes an action.
        /// </summary>
        public static Task<string> ShowAsync( FrameworkElement elem, string message, string title = null, string defaultDirectory = null, CalculateSpaceRequiredDelegate calculateSizeRequired = null )
        {
            DirectoryPicker picker = Create( elem, message, title, defaultDirectory );
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

            picker.Closed += ( object sender, EventArgs e ) =>
            {
                DirectoryPicker messageBox = (DirectoryPicker)sender;
                string directory = true.Equals( messageBox._result ) ? picker.Directory : null;
                tcs.SetResult( directory );
            };

            picker.CalculateSpaceRequired = calculateSizeRequired;

            picker.Show();
            return tcs.Task;
        }

        #endregion Public Methods

        #region Private methods

        private static DirectoryPicker Create( FrameworkElement elem, string message, string title, string defaultDirectory )
        {
            return new DirectoryPicker()
            {
                Owner = GetWindow( elem ),
                Message = message,
                Title = title,
                Directory = defaultDirectory
            };
        }

        private bool GetFreeSpaceOnDrive( string drive, out ulong space )
        {
            DriveInfo driveInfo;

            try
            {
                driveInfo = new DriveInfo( drive );
            }
            catch ( ArgumentException )
            {
                space = 0;
                return false;
            }

            if( driveInfo.IsReady )
            {
                space = (ulong)driveInfo.AvailableFreeSpace;
            }
            else
            {
                space = 0;
            }

            return true;
        }

        private async void RecalculateSpaceAvailable()
        {
            SpaceAvailableCalculating = true;

            try
            {
                _driveLetter = Path.GetPathRoot( Directory );
            }
            catch ( ArgumentException )
            {
                return;
            }

            bool calculated = false;

            SpaceAvailable = await Task.Run( () => { ulong spaceAvailable; calculated = GetFreeSpaceOnDrive( _driveLetter, out spaceAvailable ); return spaceAvailable; } );

            SpaceAvailableCalculating = !calculated;
        }

        private async void RecalculateSpaceRequired()
        {
            SpaceRequiredCalculating = true;

            CalculateSpaceRequiredDelegate func = CalculateSpaceRequired;
            SpaceRequired = await Task.Run( () => { return func(); } );

            SpaceRequiredCalculating = false;
        }

        #endregion Private methods

        #region Event handlers

        private void AffirmativeButton_Click( object sender, RoutedEventArgs e )
        {
            _result = true;
            Close();
        }

        private void NegativeButton_Click( object sender, RoutedEventArgs e )
        {
            _result = false;
            Close();
        }

        private static void OnDirectoryChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            DirectoryPicker picker = (DirectoryPicker)d;
            picker.RecalculateSpaceAvailable();
        }

        #endregion Event handlers

        #region Delegates

        public delegate ulong CalculateSpaceRequiredDelegate();

        #endregion Delegates
    }
}
